//    PAM Quiz
//    A PAM module that quizes the agent that is authenticating themselves with simple
//    questions.
//    Copyright (C) 2009 Ryan Latta
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>

#define PAM_SM_ACCOUNT /**< This define tells the PAM system that this is an account module */
#include <security/pam_modules.h>

//#define MODULE_NAME pam_quiz
/**
 * Pam_quiz.c is a PAM module that serves a simple purpose.  It is an account management module
 * requries that users respond to simple (math) questions before being allowed to proceed.  There
 * are variables to adjust how many questions to ask as well as a percentage needed to be correct.
 * 
 * If one answers too few questions correctly, they will not be authorized, and if they do, they
 * will be authorized.
**/
/*
  TODO handle arguments from pam.conf files
  TODO Make sure that when its done you set that this module succeeded or failed for other modules
  TODO Make PAM Compliant
  TODO test test test
*/

/**
 * setMessage is a simple function that does the job to filling up the pam_message structure for
 * me.  It exists just out of convenience.
 * @param type This is a #define from the pam headers for the conversation functions to use.
 * @param msg This is the string that you want sent to the user.
 * @param message This is the struct pam_message that is used to converse with.  
**/
void setMessage(int type, const char *, struct pam_message *);

/**
 * converse handles the pam_converse() funciton.
 * Essentially this function is a wrapper to handle all communication from the module to the
 * calling application.  It checks for errors and sets the appropriate response structure for you.
 * This was all pretty much taken straight from pam_stres.c
 *
 * @param pamh This is the pointer to the pam environment.
 * @param args This tells the converse function which message in teh message structure to use.  Best
 * just to use it as 1.
 * @param response is the response from the conversation.
 * @return PAM_SUCCESS if everything works well, or various PAM errors otherwise.
 * @see <security/pam_modules.h>
 * @see pam_stress.c
**/
int converse (pam_handle_t *pamh, int args, struct pam_message **message, struct pam_response **repsonse);

/**
 * A structure to contain a question.
 * This structure contains the question as a string, and the answer as a string.  So far they 
 * are hard-coded for being simple math questions.  Future work would include making them dynamic.
 * for other question types.
 **/
struct Question {
  char question[9]; /**< Holds a string like "x + y = " **/
  char answer[4]; /**< This contains the answer.  It is 4 characters for division. 1.20 for example **/
  //int question_size;
  //int answer_size;
};

/**
 * makeMathQuestion does the work of creating the mathQuestions to be asked.
 * So The process is pretty straightforward.  We generate some random numbers between 1 & 10.
 * These numbers correspond to the question x + y = ?  The operand used is also a random number
 * generated as well.  As of writing this, the division Operand is not used.  It is a pain.
 * @param q A question structure used to hold the question and answer this function generates.
**/
void makeMathQuestion(struct Question *);

void makeMathQuestion(struct Question *q) {
  const char *operands = "+-*/";
  srand(time(NULL));
  int number1, number2, operand;
  
  operand = rand() % 3;
  number1 = rand() % (10 - 1) + 1;
  number2 = rand() % (10 - 1) + 1;
  //syslog(LOG_DEBUG, "pam_quiz: makeMathQuestion(): random numbers generated...");
  
  sprintf(q->question, "%d %c %d = ", number1, operands[operand], number2);
  
  //syslog(LOG_DEBUG, "pam_quiz: makeMathQuestion(): question assigned... %s", q->question);
  
  switch (operand) {
    case 0:
      sprintf(q->answer, "%d", number1 + number2);
      break;
    case 1:
      sprintf(q->answer, "%d", number1 - number2);
      break;
    case 2:
      sprintf(q->answer, "%d", number1 * number2);
      break;
    case 3: 
      sprintf(q->answer, "%.2f", (float)number1 / number2);
      syslog(LOG_DEBUG, "pam_quiz: makeMathQuestion(): answer assigned... %s", q->answer);
      break;
    default:
      operand = 2;
  }

}

int converse (pam_handle_t *pamh, int args, struct pam_message **message, struct pam_response **response) {
	int retval;
	const void *void_conv = NULL;
	const struct pam_conv *conv = NULL;

	retval = pam_get_item(pamh, PAM_CONV, &void_conv);
	conv = (struct pam_conv*)void_conv;

	if(retval == PAM_SUCCESS && conv) {
		retval = conv->conv(args, (const struct pam_message **) message, response, conv->appdata_ptr);
		if(retval != PAM_SUCCESS) {
		syslog(LOG_ERR, "pam_quiz: converse returned %d: %s", retval, pam_strerror(pamh, retval));
		}
	}
	else {
    syslog(LOG_ERR, "pam_quiz: converse failed to get pam conv function");
		if(retval == PAM_SUCCESS) {
			retval = PAM_BAD_ITEM; /* conv was null */
		}		
	}
  return retval;	 
}

/**
  * setMessage simply does the job of setting up the message structure.
  * Just here for convenience.
 **/
void setMessage(int type, const char *msg, struct pam_message *message) {
  message->msg_style = type;
  message->msg = msg;
}
/**
 * pam_sm_acct_mgmt is the method PAM will use to do account authorization.
 * The purpose of this method is to simply ask questions to the user randomly
 * and assess if they are human or not.  What this boils down to is if they can
 * get x out of y questions correct, they will be authorized.
 *
 * @param pamh This is the pam environment.  The pam system sets this.
 * @param flags I don't have any use for these.
 * @param argc How many argments have been passed from various pam conf files.
 * @param argvc The argments array itself.
 **/
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argvc) {
  int retval, rv, i;
  struct pam_message msg, *pmsg;
  struct pam_response *response;
  int totalQuestions = 5;
  int correctAnswers = 0;
  float threshold = 0.7;
  struct Question question; 
    
  response = NULL;
  pmsg = &msg;
  //rv = PAM_AUTH_ERR; // Just by default.
  
  /**
    Order of Events:
    -1) Get a conversation function pointer to the application (Hopefully).
    -2) Generate question based on random numbers and flags (Future).
    -3) Build the conversation functions' message structure and response structure.
      -Note: Send only one message at a time. (Compatability)
    -4) Initiate the conversation.
    -5) Check the response structure for the correct answer.
    -6) Repeat if necessary (Future).
    -7) Free the response strucutre
    -8) Return our success or failure.
  */
  //syslog(LOG_DEBUG, "pam_quiz: starting...");

  for(i = 0; i < totalQuestions; i++) {
    //syslog(LOG_DEBUG, "pam_quiz: generating question...");
    makeMathQuestion(&question);
    
    setMessage(PAM_PROMPT_ECHO_ON, question.question, &msg);
    //syslog(LOG_DEBUG, "pam_quiz: question prompt set...");
    
    retval = converse(pamh, 1, &pmsg, &response);
    //syslog(LOG_DEBUG, "pam_quiz: quesiton asked...");

    if(retval != PAM_SUCCESS) {
      //Shit, another error, bail.
      //syslog(LOG_ERR, "pam_quiz: couldn't converse!");
      rv = PAM_AUTH_ERR;
    }
    if(strcmp(question.answer, response->resp) == 0) {
        correctAnswers++;
       // syslog(LOG_DEBUG, "pam_quiz: correct answer received - %d", correctAnswers);
    }
    else {
      //syslog(LOG_DEBUG, "pam_quiz: INCORRECT answer received. %s != %s", question.answer, response->resp);
      //syslog(LOG_DEBUG, "pam_quiz: answer lengths... %d != %d", strlen(question.answer), strlen(response->resp));
    }
    response = NULL;
    memset(question.question, '\0', 9);
    memset(question.answer, '\0', 4);
  }

  if((float)correctAnswers/totalQuestions < threshold) {
    //syslog(LOG_DEBUG, "pam_quiz: informing the user of too many failed questions.");
    setMessage(PAM_TEXT_INFO, "You missed too many quesitons.", &msg);
    retval = converse(pamh, 1, &pmsg, &response);
    rv = PAM_PERM_DENIED;
  }
  else {
    //Set success information using pam_set_item
   // syslog(LOG_DEBUG, "pam_quiz: authorization granted!");
    setMessage(PAM_TEXT_INFO, "Acceptable.", &msg);
    retval = converse(pamh, 1, &pmsg, &response);
    rv = PAM_SUCCESS;
  }
  
  //free(question);
  free(response);
  //delete qf;
  
  return rv;
}
/*
 * 

#ifdef PAM_STATIC
struct pam_module _pam_quiz_modstrut = {
  "pam_quiz",
  NULL,
  NULL,
  pam_sm_acct_mgmt,
  NULL,
  NULL,
  NULL
};

#endif
*/
